package com.joy.joyusers.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.joy.joyusers.common.ErrorCode;
import com.joy.joyusers.constant.RedisKey;
import com.joy.joyusers.constant.UserConstant;
import com.joy.joyusers.excption.BusinessException;
import com.joy.joyusers.mapper.UserMapper;
import com.joy.joyusers.model.VO.PageVO;
import com.joy.joyusers.model.VO.UserSearchVO;
import com.joy.joyusers.model.domain.User;
import com.joy.joyusers.service.UserService;
import com.joy.joyusers.utils.RandomSmsNumUtils;
import com.joy.joyusers.utils.RegExpUtil;
import com.joy.joyusers.utils.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.joy.joyusers.constant.HttpStatus.ERROR_CODE;
import static com.joy.joyusers.constant.HttpStatus.SUCCESS;
import static com.joy.joyusers.constant.RedisKey.EMAIL_EXPIRED_TIME;
import static com.joy.joyusers.constant.UserConstant.USER_LOGIN_STATE;


/**
 * @author 26504
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    /**
     * 密码加密盐
     */
    private static final String SALT = "joy";

    // 构造方法注入
    private final RedisTemplate<String, Object> redisTemplate;

    private final UserMapper userMapper;

    @Autowired
    public UserServiceImpl(RedisTemplate<String, Object> redisTemplate, UserMapper userMapper) {
        this.redisTemplate = redisTemplate;
        this.userMapper = userMapper;
    }


    @Resource
    private JavaMailSender javaMailSender;

    @Resource
    private TemplateEngine templateEngine;

    @Value("${spring.mail.username}")
    private String username;

    @Override
    public Response<Long> userRegister(String account, String password, String checkPassword) {

        // 1.校验
        if (StringUtils.isAnyBlank(account, password, checkPassword)) {
            throw new IllegalArgumentException("账户或密码不能为空");
        }
        if (account.length() < 4 || account.length() > 20) {
            throw new IllegalArgumentException("账户长度为4-20个字符");
        }
        if (password.length() < 8 || checkPassword.length() < 8) {
            throw new IllegalArgumentException("密码长度不能小于8个字符");
        }

        // 账户不能包括特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(account);
        if (matcher.find()) {
            throw new IllegalArgumentException("账户不能包含特殊字符");
        }
        // 密码和校验密码相同
        if (!password.equals(checkPassword)) {
            throw new IllegalArgumentException("两次输入的密码不一致");
        }
        // 账户不能重复
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("account", account);
        long count = this.count(wrapper);
        if (count > 0) {
            throw new IllegalArgumentException("该账户已被注册");
        }
        // 2.密码加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
        // 3.注册用户
        User user = new User();
        user.setAccount(account);
        user.setPassword(encryptPassword);
        boolean save = this.save(user);
        if (!save) {
            throw new IllegalArgumentException("注册失败");
        }
        // 4.返回用户id
        return new Response<>(SUCCESS, "注册成功", user.getId());

    }

    @Override
    public User doLogin(String account, String password, HttpServletRequest request) {
        // 校验
        if (StringUtils.isAnyBlank(account, password)) {
            throw new IllegalArgumentException("账户或密码不能为空");
        }
        if (account.length() < 4 || account.length() > 20) {
            throw new IllegalArgumentException("账户长度为4-20个字符");
        }
        if (password.length() < 8) {
            throw new IllegalArgumentException("密码长度不能小于8个字符");
        }
        // 账户不能包括特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(account);
        if (matcher.find()) {
            throw new IllegalArgumentException("账户不能包含特殊字符");
        }
        // 密码加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("account", account);
        wrapper.eq("password", encryptPassword);
        User user = this.getOne(wrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, account:{}", account);
            throw new IllegalArgumentException("账户或密码错误");
        }

        // 用户脱敏
        User safetyUser = getSafetyUser(user);
        // 记录用户登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
        return safetyUser;
    }

    /**
     * 用户脱敏
     *
     * @param user 用户信息
     * @return 脱敏后的用户信息
     */
    @Override
    public User getSafetyUser(User user) {
        if (user == null) {
            throw new IllegalArgumentException("用户信息不能为空");
        }
        User safetyUser = new User();
        safetyUser.setId(user.getId());
        safetyUser.setUsername(user.getUsername());
        safetyUser.setAccount(user.getAccount());
        safetyUser.setAvatar(user.getAvatar());
        safetyUser.setProfile(user.getProfile());
        safetyUser.setRole(user.getRole());
        safetyUser.setGender(user.getGender());
        safetyUser.setPhone(user.getPhone());
        safetyUser.setEmail(user.getEmail());
        safetyUser.setStatus(0);
        safetyUser.setCreateTime(user.getCreateTime());
        safetyUser.setTags(user.getTags());
        return safetyUser;
    }


    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 移除登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

    @Override
    public User getUserById(Long userId) {
        System.out.println("正在获取"+userId+"的用户信息");
        if (userId == null) {
            throw new IllegalArgumentException("用户id不能为空");
        }
        User user = this.getById(userId);
        // 用户状态为 1 时，表示用户已封号
        if (user.getStatus() == 1) {
            throw new IllegalArgumentException("用户已被封号");
        }
        return user;
    }

    @Override
    public Response<Boolean> getEmailCode(String email) {
        // 校验邮箱
        RegExpUtil.regExpVerify(RegExpUtil.emailRegExp, email, "邮箱格式错误");
        // 从redis中查看有没有该邮箱的验证码
        String verifyCode = (String) redisTemplate.opsForValue().get(RedisKey.EMAIL_CAPTCHA_CODE + email);
        if (!StringUtils.isAnyBlank(verifyCode)) {
            return new Response<>(SUCCESS, "验证码已发送，请勿重复点击", true);
        }
        // 如果redis没有该手机号验证码，则获取验证码并发送短信
        verifyCode = RandomSmsNumUtils.getSixBitRandom(); // 获取六位验证码
        sendMessageToEmail(verifyCode, email);
        // 将该验证码存入redis
        redisTemplate.opsForValue().set(
                RedisKey.EMAIL_CAPTCHA_CODE + email,
                verifyCode,
                EMAIL_EXPIRED_TIME,
                TimeUnit.MINUTES);
        return new Response<>(SUCCESS, "验证码发送成功", true);
    }

    @Override
    public Response<Boolean> forgetPassword(String email, String captcha, String password, String account) {
        // 校验邮箱
        RegExpUtil.regExpVerify(RegExpUtil.emailRegExp, email, "邮箱格式错误");
        // 只能输入0-9的数字且必须是六位数
        RegExpUtil.regExpVerify(RegExpUtil.captchaRegExp, captcha, "验证码格式错误");
        // 从redis中查看是否有验证码
        String verifyCode = (String) redisTemplate.opsForValue().get(RedisKey.EMAIL_CAPTCHA_CODE + email);
        // 如果没有验证码
        if (StringUtils.isBlank(verifyCode)) {
            throw new IllegalArgumentException("验证码已过期，请重新获取");
        }
        // 如果验证码不同
        if (!verifyCode.equals(captcha)) {
            throw new IllegalArgumentException(ERROR_CODE);
        }
        // 如果验证码相同，删除redis中的验证码
        redisTemplate.delete(RedisKey.EMAIL_CAPTCHA_CODE + email);
        // 密码加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
        // 修改密码
        User user = new User();
        user.setPassword(encryptPassword);
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("email", email)
                .eq("account", account);
        boolean update = this.update(user, wrapper);
        if (update) {
            return new Response<>(SUCCESS, "密码修改成功", true);
        }
        return new Response<>(SUCCESS, "密码修改失败", false);
    }

    /**
     * 分页查询用户信息
     *
     * @param pageVO       分页信息
     * @param userSearchVO 用户信息
     * @return 用户信息
     */
    @Override
    public Response<List<UserSearchVO>> getQueryPage(PageVO pageVO, UserSearchVO userSearchVO) {
        Page<User> page = new Page<>(pageVO.getCurrent(), pageVO.getSize());
        // 条件查询
        User user = new User();
        // Use Hutool's rtBeanUtil to copy properties from UserSearchVO to User
        BeanUtil.copyProperties(userSearchVO, user);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.like(Strings.isNotEmpty(userSearchVO.getAccount()), User::getAccount, user.getAccount())
                .like(Strings.isNotEmpty(userSearchVO.getUsername()), User::getUsername, user.getUsername())
                .like(Strings.isNotEmpty(userSearchVO.getPhone()), User::getPhone, user.getPhone())
                .like(Strings.isNotEmpty(userSearchVO.getEmail()), User::getEmail, user.getEmail())
                .like(userSearchVO.getGender() != null, User::getGender, user.getGender())
                .like(userSearchVO.getRole() != null, User::getRole, user.getRole())
                .like(userSearchVO.getStatus() != null, User::getStatus, user.getStatus())
                .like(userSearchVO.getCreateTime() != null, User::getCreateTime, user.getCreateTime())
                .like(userSearchVO.getUpdateTime() != null, User::getUpdateTime, user.getUpdateTime())
                .like(userSearchVO.getId() != null, User::getId, user.getId());


        IPage<User> userIPage = userMapper.selectPage(page, lqw);
        // 使用Java 8的流API将每个User对象转换为UserSearchVO对象，并收集到新的List中
        List<UserSearchVO> list = userIPage.getRecords().stream()
                .map(this::convertToUserSearchVO)
                .collect(Collectors.toList());
        Long count = userMapper.selectCount(lqw);
        return new Response<>(SUCCESS, "查询成功", list, count);
    }

    @Override
    public Response<Boolean> updateUser(User user) {
        // 校验
        if (user == null) {
            return new Response<>(SUCCESS, "保存成功", true);
        }
        // 更新用户信息
        boolean update = this.updateById(user);
        if (update) {
            return new Response<>(SUCCESS, "保存成功", true);
        }
        return new Response<>(SUCCESS, "保存失败", false);
    }

    /**
     * 修改用户信息，在个人页面
     * @param user 传入的参数用户信息
     * @param userInfo 当前登录的用户
     * @return
     */
    @Override
    public int modifyUser(User user, User userInfo) {
        long userId = userInfo.getId();
        if (userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 这里要判断一下，因为没有做传递过来的值是空也能更新的校验
        // 想了一下还是在前端判断好一点

        // 如果是管理员，允许更新任意用户
        // 如果不是管理员，只允许更新自己的信息 ：&& 两个都为true 才执行
        if (!isAdmin(userInfo) && userInfo.getId() != user.getId()) {  // 如果它不是管理员，而且改的也不是自己的信息，就抛出异常
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        // 查一下有没有再更新
        User olderUser = userMapper.selectById(userId);
        if(olderUser == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        return userMapper.updateById(user);
    }

    @Override
    public Response<Boolean> deleteUser(Long userId) {
        // 校验
        if (userId == null) {
            throw new IllegalArgumentException("用户id不能为空");
        }
        // 删除用户
        boolean remove = this.removeById(userId);
        if (remove) {
            return new Response<>(SUCCESS, "删除成功", true);
        }
        return new Response<>(SUCCESS, "删除失败", false);
    }


    /**
     * 发送短信验证码
     *
     * @param verifyCode 验证码
     * @param email      手机号
     */
    private void sendMessageToEmail(String verifyCode, String email) {
        // 发送邮件验证码
        Context context = new Context(); // 引入Template的Context
        // 设置模板中的变量（分割验证码）
        context.setVariable("verifyCode", Arrays.asList(verifyCode.split("")));
        // 第一个参数为模板的名称(html不用写全路径)
        String process = templateEngine.process("EmailCode.html", context); // 这里不用写全路径
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
            helper.setSubject("【Joy Admin】验证码"); // 邮件的标题
            helper.setFrom(username); // 发送者
            helper.setTo(email); // 接收者
            helper.setSentDate(new Date()); // 时间
            helper.setText(process, true); // 第二个参数true表示这是一个html文本
        } catch (MessagingException e) {
            throw new IllegalArgumentException("发送邮件失败");
        }
        javaMailSender.send(mimeMessage);

    }

    private UserSearchVO convertToUserSearchVO(User user) {
        UserSearchVO userSearchVO = new UserSearchVO();
        // 将User对象的属性复制到UserSearchVO对象中
        // 这里假设UserSearchVO类有与User类相对应的属性和getter/setter方法
        userSearchVO.setId(user.getId());
        userSearchVO.setAccount(user.getAccount());
        userSearchVO.setUsername(user.getUsername());
        userSearchVO.setEmail(user.getEmail());
        userSearchVO.setAvatar(user.getAvatar());
        userSearchVO.setPhone(user.getPhone());
        userSearchVO.setGender(user.getGender());
        userSearchVO.setRole(user.getRole());
        userSearchVO.setStatus(user.getStatus());
        userSearchVO.setCreateTime(user.getCreateTime());
        userSearchVO.setUpdateTime(user.getUpdateTime());
        return userSearchVO;
    }

    /**
     * 根据标签搜索用户
     * @param tagNameList
     * @return
     */
    @Override
    public List<User> searchUserByTags(List<String> tagNameList) {
        // 判断一下是否为空
        if(CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数标签列表不能为空");
        }
        // sql查询开始时间
        long startTime = System.currentTimeMillis();
        // 构建查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 拼接查询
        for(String tags : tagNameList) {
            // 会自动加上百分号操作
            queryWrapper = queryWrapper.like("tags", tags);
        }
        // 调用mapper查询
        List<User> usersList = userMapper.selectList(queryWrapper);
        // 判空一下
        if(CollectionUtils.isEmpty(usersList)) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "查询的用户列表为空");
        }
        // sql查询结束时间
        log.info("sql执行时间：{}", System.currentTimeMillis() - startTime);
        // 遍历数据脱敏
        return usersList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }


    /**
     * 是否为管理员（从当前登录用户中获取）
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && user.getRole() == UserConstant.ADMIN_ROLE;
    }

    /**
     * 是否为管理员（传入某个用户User）
     *
     * @param loginUser
     * @return
     */
    @Override
    public boolean isAdmin(User loginUser) {
        return loginUser != null && loginUser.getRole() == UserConstant.ADMIN_ROLE;
    }

    /**
     * 根据标签搜索用户(内存查询)
     * @param tagNameList
     * @return
     */
    @Override
    public List<User> searchUserByTags2(List<String> tagNameList) {
        // 判断一下是否为空
        if(CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "标签列表不能为空");
        }


        // 1. 先直接在内存里面查询，首先线查询所有用户呗
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> usersList = userMapper.selectList(queryWrapper);
        Gson gson = new Gson();

        // 2. 过滤掉不满足条件的user
        return usersList.stream().filter(user -> {
            String tagStr = user.getTags();
            // 这里从数据库取出来的tags也得校验一下，不然会出现空指针的错误
            if (StringUtils.isBlank(tagStr)) {
                log.info("tags为空");
                return false;
            }
            // 用一个库来解析json字符串，把json转换为java对象，反序列化； 序列化：把java对象转换成json
            Set<String> tempNameList = gson.fromJson(tagStr, new TypeToken<Set<String>>(){}.getType());
            // 判断是否为空，Optional 是 Java 8 引入的一个容器对象，它可能包含也可能不包含非null的值。它提供了一种更优雅的方式来处理可能为null的值
            // orElse 方法用于获取 Optional 对象中的值，如果 Optional 对象为空，则返回指定的默认值。
            tempNameList = Optional.ofNullable(tempNameList).orElse(new HashSet<>());
            // 拿到一个标签的集合
            for (String tags : tagNameList) {
                // 判断是否包含
                if(!tempNameList.contains(tags)){
                    log.info("tags不包含");
                    // 不包含就直接返回false
                    return false;
                }
            }
            return true;
        }).map(this::getSafetyUser).collect(Collectors.toList());
    }
    // 这个时候查询条件是没有的，也就是说执行的是全量查询，看日志也能看出来

    /**
     * 封装一个方法获取当前登录用户信息
     * @return
     */
    @Override
    public User getCurrentLoginUser(HttpServletRequest request) {
        if(request == null) {
            return null;
        }
        // 如何获取：从请求信息中拿到cokie
        Object userinfo = request.getSession().getAttribute(USER_LOGIN_STATE);
        if (userinfo == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        // 转换一下
        return (User) userinfo;
    }


}




